﻿using System;
using System.Diagnostics;
using System.Collections.Generic;
using System.Linq;
using System.Collections.Concurrent;
using System.Threading.Tasks;

using ADLite;

using Ecng.Common;
using Ecng.ComponentModel;

using StockSharp.BusinessEntities;
using StockSharp.AlfaDirect.Tables;

namespace StockSharp.AlfaDirect
{

    /// <summary>
    /// Аттрибут используемый для извлечения информации обо всех существующих таблицах во враппере.
    /// Он поможет избежать ошибок при добавлении новых таблиц или удаления существующих. По аттрибуту можно
    /// всегда проверить существование таблицы с необходимым именем во враппере или тупо перебрать их всех и 
    /// выполнить нужный метод (например отписаться от всех таблиц в StopExport)
    /// </summary>
    [System.AttributeUsage(AttributeTargets.Property)]
    internal class TblAttribute : System.Attribute
    {
        /// <summary>
        /// Реальное имя таблицы. Имя с которым работает Альфа API.
        /// </summary>
        public string RealName { get; set; }
    }


	sealed class AlfaWrapper : IAlfaWrapper
	{
        //private const string _logTag = "AlfaWrapper";
        #region Fields

        /// <summary>
        /// Объект для глобальной блокировки методов доступа к СОМ объекту терминала Альфы.
        /// </summary>
	    private readonly object _globalLock = new object();

        private readonly AlfaDirectClass _ad;
        /// <summary>
        /// Таблица Ордеров
        /// </summary>
        private readonly AdTable _tblOrders;
        /// <summary>
        /// Таблица Портфелей и Позиций
        /// </summary>
        private readonly AdTable _tblBalance;
        /// <summary>
        /// Таблица сделок по инструментам.
        /// </summary>
        private readonly AdTable _tblAllTrades;
        /// <summary>
        /// Таблица Собственных сделок
        /// </summary>
        private readonly AdTable _tblMyTrades;
        /// <summary>
        /// Таблица Стакана
        /// </summary>
        private readonly AdQueueTable _tblQueue;
        /// <summary>
        /// Таблица Котировок инструментов.
        /// </summary>
        private readonly AdTable _tblFinInfo;
        /// <summary>
        /// Таблица Справочник финансовых инструментов.
        /// </summary>
        private readonly CachedAdTable _tblPapers;
        /// <summary>
        /// Таблица Справочник рынков.
        /// </summary>
        private readonly CachedAdTable _tblTradePlaces;


	    private BlockingCollection<BaseCommand> _orderQueue;
        private BlockingCollection<OrderFail> _registerOrderFailedQueue;
        private BlockingCollection<OrderFail> _cancelOrderFailedQueue;
        private BlockingCollection<TableDataPortion> _tablesQueue;


        #endregion Fields

        #region Properties

        /// <summary>
        /// Версия COM объекта терминала.
        /// </summary>
        public string Version
        {
            get { return (string)_ad.Version; }
        }
        /// <summary>
        /// Текущее состояние подключения терминала.
        /// </summary>
        public bool IsConnected
        {
            get { return _ad.Connected; }
        }
        /// <summary>
        /// Возвращает текущее рыночное время с сервера АльфаДирект
        /// </summary>
        public DateTime MarketTime
        {
            get
            {
                return GetMarketTime();
            }
        }


        /// <summary>
        /// Таблица Ордеров
        /// </summary>
        [TblAttribute(RealName = "orders")]
        public AdTable TblOrders 
        {
            get { return _tblOrders; }
        }
        /// <summary>
        /// Таблица Портфелей и Позиций
        /// </summary>
        [TblAttribute(RealName = "balance")]
        public AdTable TblBalance
        {
            get { return _tblBalance; }
        }
        /// <summary>
        /// Таблица сделок по инструментам.
        /// </summary>
        [TblAttribute(RealName = "all_trades")]
        public AdTable TblAllTrades
        { 
            get { return _tblAllTrades; } 
        }
        /// <summary>
        /// Таблица Собственных сделок
        /// </summary>
        [TblAttribute(RealName = "trades")]
        public AdTable TblMyTrades
        { 
            get { return _tblMyTrades; } 
        }
        /// <summary>
        /// Таблица Стакана
        /// </summary>
        [TblAttribute(RealName = "queue")]
        public AdQueueTable TblQueue
        { 
            get { return _tblQueue; } 
        }
        /// <summary>
        /// Таблица Котировок инструментов.
        /// </summary>
        [TblAttribute(RealName = "fin_info")]
        public AdTable TblFinInfo
        { 
            get { return _tblFinInfo; } 
        }
        /// <summary>
        /// Таблица Справочник финансовых инструментов.
        /// </summary>
        [TblAttribute(RealName = "papers")]
        public AdTable TblPapers
        { 
            get { return _tblPapers; } 
        }
        /// <summary>
        /// Таблица Справочник рынков.
        /// </summary>
        [TblAttribute(RealName = "trade_places")]
        public AdTable TblTradePlaces
        {
            get { return _tblTradePlaces; } 
        }

        #endregion Properties

        #region Events

        /// <summary>
        /// Событие вызываемое когда терминал подключился к серверу Альфы
        /// </summary>
        public event Action ConnectedEvent;
        /// <summary>
        /// Событие вызываемое когда терминал отключился от сервера Альфы.
        /// </summary>
        public event Action DisconnectedEvent;
        /// <summary>
        /// Событие вызываемое при ошибке подключения терминала к серверу Альфы.
        /// </summary>
        public event Action<Exception> ConnectionErrorEvent;
        /// <summary>
        /// Событие вызываемое при любом получении новых данных от Альфа терминала.
        /// </summary>
        public event Action NewDataExportedEvent;
        /// <summary>
        /// Событие вызывается при ошибках обработки поступающих данных от терминала.
        /// </summary>
	    public event Action<Exception> ProcessDataErrorEvent;

	    public event Action<IEnumerable<OrderFail>> OrdersRegisterFailedEvent;
        public event Action<IEnumerable<OrderFail>> StopOrdersRegisterFailedEvent;
        public event Action<IEnumerable<OrderFail>> OrdersCancelFailedEvent;
        public event Action<IEnumerable<OrderFail>> StopOrdersCancelFailedEvent;


        #endregion Events



        /// <summary>
        /// 
        /// </summary>
		public AlfaWrapper()
		{
			_ad = new AlfaDirectClass();
			_ad.OnConnectionChanged += ConnectionChanged;
			_ad.OnTableChanged += TableChanged;
            _ad.OrderConfirmed += OnOrderConfirmed;

            StartQueues();

            // TODO: подклюить события об ошибках от таблиц, к событию враппера.

            //"ord_no, acc_code, paper_no, status, b_s, price, qty, rest, ts_time, comments, place_code, stop_price, avg_trd_price";
            _tblOrders = new AdTable(name: "orders",
                                     columns: new List<string>(new string[] {"ord_no", 
                                                                             "acc_code", 
                                                                             "paper_no", 
                                                                             "status", 
                                                                             "b_s", 
                                                                             "price", 
                                                                             "qty", 
                                                                             "rest", 
                                                                             "ts_time",
                                                                             "last_date",
                                                                             "drop_time",
                                                                             "comments", 
                                                                             "place_code", 
                                                                             "stop_price", 
                                                                             "blank",
                                                                             "actv_grow_price",
                                                                             "actv_down_price",
                                                                             "updt_grow_price",
                                                                             "updt_down_price",
                                                                             "updt_new_price",
                                                                             "trailing_level",
                                                                             "trailing_slippage",
                                                                             "init_price",
                                                                             "avg_trd_price"}
                                                              ),
                                    tableSubscriber: SubscribeTable,
                                    tableUnSubscriber: UnSubscribeTable,
                                    tableReader: ReadTable
            );
            //"acc_code, p_code, place_code, real_rest, daily_pl, paper_no, netto_rest, prev_close";
            _tblBalance = new AdTable(name: "balance",
                                      columns: new List<string>(new string[] {"acc_code", 
                                                                              "p_code", 
                                                                              "place_code", 
                                                                              "paper_no",
                                                                              "daily_pl",
                                                                              "real_rest", 
                                                                              "forword_rest", 
                                                                              "netto_rest", 
                                                                              "prev_close"}
                                                               ),
                                      tableSubscriber: SubscribeTable,
                                      tableUnSubscriber: UnSubscribeTable,
                                      tableReader: ReadTable
            );
            // "trd_no, paper_no, qty, price, ts_time, b_s"
            _tblAllTrades = new AdTable(name: "all_trades",
                                        columns: new List<string>(new string[] {"trd_no", 
                                                                                "paper_no", 
                                                                                "qty", 
                                                                                "price", 
                                                                                "ts_time", 
                                                                                "b_s"}
                                                                 ),
                                        tableSubscriber: SubscribeTable,
                                        tableUnSubscriber: UnSubscribeTable,
                                        tableReader: ReadTable
            );
            //"trd_no, ord_no, treaty, paper_no, price, qty, b_s, ts_time";
            _tblMyTrades = new AdTable(name: "trades",
                                       columns: new List<string>(new string[] {"trd_no", 
                                                                               "ord_no", 
                                                                               "treaty", 
                                                                               "paper_no", 
                                                                               "price", 
                                                                               "qty", 
                                                                               "b_s", 
                                                                               "ts_time"}
                                                                ),
                                       tableSubscriber: SubscribeTable,
                                       tableUnSubscriber: UnSubscribeTable,
                                       tableReader: ReadTable
            );
            //"sell_qty, price, buy_qty"
            _tblQueue = new AdQueueTable(name: "queue",
                                    columns: new List<string>(new string[] {"paper_no",
                                                                            "sell_qty", 
                                                                            "price", 
                                                                            "buy_qty"}
                                                                ),
                                    tableSubscriber: SubscribeQueueTable,
                                    tableUnSubscriber: UnSubscribeTable,
                                    tableReader: ReadQueueTable
            );
            //Create = "paper_no, ANSI_name, mat_date, status, p_code, place_code, curr_code, go_buy, go_sell, board_code, open_pos_qty, open_price, close_price";
            //Update = "paper_no, sell, sell_qty, buy, buy_qty, min_deal, max_deal, open_pos_qty, open_price, close_price";
            _tblFinInfo = new AdTable(name: "fin_info",
                                      columns: new List<string>(new string[] {"paper_no",
                                                                              "p_code",
                                                                              "ANSI_name", 
                                                                              "mat_date", 
                                                                              "status", 
                                                                              "place_code", 
                                                                              "curr_code", 
                                                                              "go_buy", 
                                                                              "go_sell", 
                                                                              "board_code", 
                                                                              "open_pos_qty", 
                                                                              "open_price", 
                                                                              "close_price",
                                                                              "sell", 
                                                                              "sell_qty", 
                                                                              "buy", 
                                                                              "buy_qty", 
                                                                              "min_deal", 
                                                                              "max_deal",
                                                                              "lot_size"}
                                                               ),
                                      tableSubscriber: SubscribeTable,
                                      tableUnSubscriber: UnSubscribeTable,
                                      tableReader: ReadTable
            );

            _tblPapers = new CachedAdTable(name: "papers",
                                     columns: new List<string>(new string[] {"paper_no", 
                                                                            "p_code", 
                                                                            "lot_size", 
                                                                            "price_step", 
                                                                            "price_step_cost"}
                                                              ),
                                     tableSubscriber: SubscribeTable,
                                     tableUnSubscriber: UnSubscribeTable,
                                     tableReader: ReadTable,
                                     maxCacheTime: new TimeSpan(0, 1, 0)
            );

            _tblTradePlaces = new CachedAdTable(name: "trade_places",
                                          columns: new List<string>(new string[] {"place_code", 
                                                                                  "curr_code", 
                                                                                  "place_name", 
                                                                                  "info_price", 
                                                                                  "status", 
                                                                                  "ex_code", 
                                                                                  "i_last_update", 
                                                                                  "allow_trade", 
                                                                                  "vendor_code", 
                                                                                  "dp_name", 
                                                                                  "quote_place_code", 
                                                                                  "allow_all_trades"}
                                                                   ),
                                          tableSubscriber: SubscribeTable,
                                          tableUnSubscriber: UnSubscribeTable,
                                          tableReader: ReadTable,
                                          maxCacheTime: new TimeSpan(0, 1, 0)
            );
		}

        ~AlfaWrapper()
        {
            StopQueues();
        }

        /// <summary>
        /// Вызов события об ошибке обработки данных полученных от терминала.
        /// </summary>
        /// <param name="exception">Исключение вызыванное при ошибке.</param>
        private void RaiseProcessDataError(Exception exception)
        {
            ProcessDataErrorEvent.SafeInvoke(exception);
        }
	    /// <summary>
        /// Производит подключение к серверу Альфы. Если логин и пароль не заданы, то ничего не делает.
        /// </summary>
        /// <param name="login">Логин.</param>
        /// <param name="password">Пароль.</param>
        public void Connect(string login, string password)
        {
            if (!string.IsNullOrWhiteSpace(login) && !string.IsNullOrWhiteSpace(password))
            {
                _ad.UserName = login;
                _ad.Password = password;
                _ad.Connected = true;
            }
        }
        /// <summary>
        /// Отключает от сервера Альфы
        /// </summary>
        public void Disconnect()
        {
            _ad.Connected = false;
        }
        /// <summary>
        /// Метод вызываемый по событию изменения состояния подключения терминала к серверу Альфы
        /// </summary>
        /// <param name="state">Новое состояние подключения терминала к серверу Альфы</param>
        private void ConnectionChanged(eConnectionState state)
        {
            switch (state)
            {
                case eConnectionState.Connected:
                    ConnectedEvent.SafeInvoke();
                    break;
                case eConnectionState.Disconnected:
                    DisconnectedEvent.SafeInvoke();
                    break;
                default:
                    ConnectionErrorEvent.SafeInvoke(new Exception("Error eConnectionState: " + state));
                    break;
            }
        }

	    /// <summary>
	    /// Событие подтверждения присвоения ордеру номера на бирже. Приходит если время ожидания ордера было поставлено -1.
	    /// Когда на бирже ордеру присваивается номер, вызывается это событие.
	    /// </summary>
        /// <param name="ordId">Внутренний номер заявки, которые при отправке назначается терминалом. </param>
	    /// <param name="orderNum">Номер заявки присвоенный биржей.</param>
	    /// <param name="message">Текстовое сообщение.</param>
	    /// <param name="status">Результат выставления заявки.</param>
	    private void OnOrderConfirmed(int ordId, int orderNum, string message, eCommandResult status)
        {
            //Debug.WriteLine("Событие отработало: {0}:{1}:{2}".Put(DateTime.Now.Minute, DateTime.Now.Second, DateTime.Now.Millisecond));

            #region Проверка ожидающих заявок на Регистрацию ордера
            
            // ищем заявку по ordId, если нашли значит это была заявка на Регистрацию ордера.
            var pair = _confirmationWaitingOrders.FirstOrDefault(p => p.Id == ordId);
            if (pair != null)
            {
                // если номер ордера больше 0, значит биржа приняла заявку. так же статус должен быть success. Если 0, то неприняла.
                // Если биржа приняла заявку, просто поставим ей Номер. В другом случае сгенерируем событие ошибки регистрации
                if (orderNum > 0)
                {
                    pair.Order.Id = orderNum;
                }
                else
                {
                    // генерация события об ошибке. Пробуем добавить ошибочный ордер в очередь ордеров в течение 2 секунд.
                    // большее время будет вешать терминал.
                    var exception = new Exception(message);
                    var orderFail = new OrderFail { Order = pair.Order, Error = exception };
                    _registerOrderFailedQueue.TryAdd(orderFail, 2000);
                    // TODO: если ордер не добавился за это время, генерить мессадж или еще что. Мы должны знать.
                }

                _confirmationWaitingOrders.Remove(pair);
                return;
            }

            #endregion

            #region Проверка ожидающих заявок на Отмену ордера

            // если не нашли по ordId значит это была отмена заявки, надо искать по номеру ордера
            pair = _confirmationWaitingOrders.FirstOrDefault(p => p.Order.Id == orderNum);
            if(pair != null)    
            {
                if (status == eCommandResult.crSuccess)
                {
                    // успешная отмена заявки. Делать нихрена не нужно.
                }
                else
                {
                    // генерация события об ошибке. Пробуем добавить ошибочный ордер в очередь ордеров в течение 2 секунд.
                    // большее время будет вешать терминал.
                    var exception = new Exception(message);
                    var orderFail = new OrderFail { Order = pair.Order, Error = exception };
                    _cancelOrderFailedQueue.TryAdd(orderFail, 2000);
                    // TODO: если ордер не добавился за это время, генерить мессадж или еще что. Мы должны знать.
                }

                // удаляем запись об ордере из очереди ожидания.
                _confirmationWaitingOrders.Remove(pair);
                return;
            }

            #endregion
            
            
            // если заявка не найдена ни по ordId ни по номеру ордера, значит она прошла из терминала или еще как. Ничего не делаем.
        }

	    
        /// <summary>
        /// Метод вызываемый каждый раз при получения события OnTableChanged от терминала Альфы. 
        /// Метод обрабатывает полученные данные и передает их в соответствующую абстрактную таблицу для передачи подписчикам.
        /// </summary>
        /// <param name="tableName">Имя таблицы вызвавшей событие</param>
        /// <param name="tableParams">Фильтр накладываемый на данные при генерации события. Далеко не всегда он реально работает.</param>
        /// <param name="data">Собственно данные передаваемые событием, разделенные знаком '|'. 
        /// Пример: 0|997269,91|997269,91|</param>
        /// <param name="fieldtypes">Названия типов для каждого поля в данных.</param>
        private void TableChanged(string tableName, string tableParams, object data, object fieldtypes)
		{
            // Debug.WriteLine("Пришли данные {0}:".Put(DateTime.Now.ToLongTimeString()));
            // Debug.WriteLine((string)data);


            // добавляем данные в базовую очередь, которая дальше распихает по очередям конкретным.
            // блокировка потока в итоге минимальная.
            // TODO: похоже что создание объекта каждый раз даетбольшую нагрузку на проц.
            var dataPortion = new TableDataPortion
                                  {
                                      TableName = tableName,
                                      TableParams = tableParams,
                                      Data = data,
                                      FieldTypes = fieldtypes
                                  };
            _tablesQueue.Add(dataPortion);
		}


        #region Подписка/отписка/чтение и обработка данных от таблиц

        /// <summary>
        ///     Производит реальную подписку на событие обновления таблицы Альфы. После подписки обновление данной таблицы
        ///     начинает вызывать событие OnTableChanged. Пока толком не работают фильтры на подписку у Альфы, 
        ///     внутри метода подписка, на все таблицы кроме queue, производится вообще без фильтров. В результате события
        ///     будут вызываться чаще, чем если бы подписка проводилась с фильтром.
        /// </summary>
        /// <remarks>
        ///     Данный метод запрещено использовать для подписки на таблицу queue
        ///     потому что для нее нужен свой тип подписки, чтобы данные возвращались корректно.
        /// </remarks>
        /// <param name="tableName">Имя таблицы на которую делается подписка</param>
        /// <param name="columns">Список колонок, которые мы хотим получать</param>
        /// <param name="where">Набор правиль по которым данные должны фильтроваться при получении</param>
        /// <exception cref="ArgumentNullException">Вызывается если аргументы не заданы</exception>
        public void SubscribeTable(string tableName, IList<string> columns, FilterRules where)
        {
            if (string.IsNullOrWhiteSpace(tableName))
                throw new ArgumentNullException("tableName");

            if (where == null)
                throw new ArgumentNullException("where");

            if (columns == null)
                throw new ArgumentNullException("columns");

            // преобразуем список полей и where в текстовые строки.
            var fields = ToCommaSeparatedString(columns);
            //var sqlString = where.ToSqlString();

            // следует иметь ввиду что подписка новая, отменяет все подписки старые. То есть фильтры будут сброшены.
            // таким образом нужно учитывать предыдущие подписки и складывать фильтры через OR если они есть.
            // TODO: на текущий момент нормальная работа фильтров не реализована в альфе, поэтому не будем вообще выставлять никаких
            SubscribeTable(tableName, fields, "", eSubsctibeOptions.UpdatesOnly);
        }
	    /// <summary>
        ///     Производит реальную подписку на событие обновления таблицы Альфы. После подписки обновление данной таблицы
        ///     начинает вызывать событие OnTableChanged. Метод предназначе для подписки на таблицу Queue
        ///     потому что работа с ней отличается от работы с другими таблицами.
        /// </summary>
        /// <remarks>
        ///     Данный метод рекомендуется использовать только для таблицы Queue.
        /// </remarks>
        /// <param name="tableName">Имя таблицы на которую делается подписка</param>
        /// <param name="columns">Список колонок, которые мы хотим получать</param>
        /// <param name="where">Набор правиль по которым данные должны фильтроваться при получении</param>
        /// <exception cref="ArgumentNullException">Вызывается если аргументы не заданы</exception>
        public void SubscribeQueueTable(string tableName, IList<string> columns, FilterRules where)
        {
            if (string.IsNullOrWhiteSpace(tableName))
                throw new ArgumentNullException("tableName");

            if (where == null)
                throw new ArgumentNullException("where");

            if (columns == null)
                throw new ArgumentNullException("columns");

            // преобразуем список полей и where в текстовые строки.
            var fields = ToCommaSeparatedString(columns);
            var sqlString = where.ToSqlString();

            // Для queue фильтр по paper_no обязателен. Иначе не будут приходить данные вообще.
            // Подписка на таблицу queue возможна только в полном режиме. Так что для нее подписку делаем отдельно.
            SubscribeTable(tableName, fields, sqlString, eSubsctibeOptions.Default);
        }
        /// <summary>
        /// Реально отписывает от таблицы Альфы. После отписывания для таблицы перестают вызываться события OnTableChanged.
        /// Фильтр имеет смысл только если отписывание идет от queue, тогда он имеет значение, и если подписка была с одним
        /// фильтром, а отписываемся уже с другим фильтром, то произойдет ошибка.
        /// </summary>
        /// <param name="tableName">Имя таблицы</param>
        /// <param name="where">Фильтр, используемый при отписке</param>
        public void UnSubscribeTable(string tableName, FilterRules where)
        {
            if (string.IsNullOrWhiteSpace(tableName))
                throw new ArgumentNullException("tableName");

            if (where == null)
                throw new ArgumentNullException("where");

            // преобразуем список полей и where в текстовые строки.
            var sqlString = where.ToSqlString();

            // судя из мануала по API фильтр where имеет смысл только когда отписка идет от стакана. 
            // В остальных таблицах фильтр просто не оказывает никакого действия.
            UnSubscribeTable(tableName, sqlString);
        }
        /// <summary>
        ///     Читает таблицу, парсит полученные данные и возвращает их в готовом виде.
        ///     Метод предназначен для чтения таблиц кроме QUEUE. При чтении фильтры не используются.
        ///     Читаются все данные таблицы.
        /// </summary>
        /// <param name="tableName">Имя таблицы на которую делается подписка</param>
        /// <param name="columns">Список колонок, которые мы хотим получать</param>
        /// <param name="where">Набор правиль по которым данные должны фильтроваться при получении</param>
        /// <exception cref="ArgumentNullException">Вызывается если аргументы не заданы</exception>
        /// <returns>Прочитанные данные.</returns>
        public object ReadTable(string tableName, IList<string> columns, FilterRules where)
        {
            if (string.IsNullOrWhiteSpace(tableName))
                throw new ArgumentNullException("tableName");

            if (where == null)
                throw new ArgumentNullException("where");

            if (columns == null)
                throw new ArgumentNullException("columns");


            // преобразуем список полей и where в текстовые строки.
            var fields = ToCommaSeparatedString(columns);
            var sqlString = where.ToSqlString();

            // читаем данные из таблицы Альфы
            var data = GetLocalDbData(tableName, fields, "");

            return data;
        }
	    /// <summary>
        ///     Читает таблицу, парсит полученные данные и возвращает их в готовом виде.
        ///     Метод предназначен для чтения QUEUE. При чтении используются переданные фильтры.
        /// </summary>
        /// <param name="tableName">Имя таблицы на которую делается подписка</param>
        /// <param name="columns">Список колонок, которые мы хотим получать</param>
        /// <param name="where">Набор правиль по которым данные должны фильтроваться при получении</param>
        /// <exception cref="ArgumentNullException">Вызывается если аргументы не заданы</exception>
        /// <returns>Прочитанные данные.</returns>
        public object ReadQueueTable(string tableName, IList<string> columns, FilterRules where)
        {
            if (string.IsNullOrWhiteSpace(tableName))
                throw new ArgumentNullException("tableName");

            if (where == null)
                throw new ArgumentNullException("where");

            if (columns == null)
                throw new ArgumentNullException("columns");


            // преобразуем список полей и where в текстовые строки.
            var fields = ToCommaSeparatedString(columns);
            var sqlString = where.ToSqlString();

            // читаем данные из таблицы Альфы
	        var data = GetLocalDbData(tableName, fields, sqlString);

            return data;
        }


        /// <summary>
        /// Возвращает список элементов через запятую. Элементы берутся из списка columns
        /// </summary>
        /// <param name="columns">Список элементов. Тупо список строк. Строки null преобразуются в пустые строки</param>
        /// <returns></returns>
        /// <exception cref="ArgumentNullException"></exception>
        public string ToCommaSeparatedString(IList<string> columns)
        {
            if (columns == null)
                throw new ArgumentNullException("columns");

            var fields = string.Empty;
            if (columns.Count > 0)
            {
                for (var i = 0; i < columns.Count; i++)
                {
                    fields += "{0},".Put(columns[i]);
                }

                fields = fields.Substring(0, fields.Length - 1);
            }

            return fields;
        }

        #endregion Подписка/отписка/чтение и обработка данных от таблиц

        #region Методы обертки для методов COM объекта, с глобальной блокировкой.

        /// <summary>
        /// Обертка вокруг терминала для получения времени. При запросе времени есть блокировка.
        /// </summary>
        /// <returns>Текущее время терминала. Если подключены к рынкам, то рыночное, иначе локальное.</returns>
        private DateTime GetMarketTime()
        {
            DateTime sessionTime;
            lock (_globalLock)
            {
                sessionTime = _ad.SessionTime;
            }
            return sessionTime;
        }
        /// <summary>
        /// Обертка вокруг метода СОМ объекта терминала. Внутри происходит блокировка объекта на время выполнения метода.
        /// </summary>
        /// <param name="tableName">Имя таблицы</param>
        /// <param name="fields">список колонок</param>
        /// <param name="where">фильтр в виде sql строки</param>
        /// <returns>полученные данные</returns>
        private string GetLocalDbData(string tableName, string fields, string where)
        {
            string data;
            StateCodes result;
            string resultMessage;

            // ставим глобальную блокировку для СОМ объекта.
            lock (_globalLock)
            {
                data = _ad.GetLocalDBData(tableName, fields, where);
                result = _ad.LastResult;
                resultMessage = _ad.LastResultMsg;
            }

            if (result != StateCodes.stcSuccess)
            {
                RaiseProcessDataError(
                    new Exception("Ошибка получения данных в методе GetLocalDbData. TableName = {0}; Columns = {1}; Where = {2}. \r\n".Put(tableName, fields, where) +
                                  "Result = {0}; ResultMsg = {1}".Put(result, resultMessage)));
            }

            NewDataExportedEvent.SafeInvoke();
            return data;
        }
        /// <summary>
        /// Обертка для метода Терминала, для регистрации подписки на таблицу. Внутри ставится блокировка.
        /// </summary>
        /// <param name="tableName">Имя таблицы</param>
        /// <param name="fields">список колонок</param>
        /// <param name="where">фильтр в виде sql строки</param>
        /// <param name="options">опции подписки на таблицу</param>
        private void SubscribeTable(string tableName, string fields, string where, eSubsctibeOptions options)
        {
            string message;
            tagStateCodes code;

            // ставим глобальную блокировку для СОМ объекта.
            lock (_globalLock)
            {
                code = _ad.SubscribeTable(tableName, fields, where, options, out message);
            }


            if (code != tagStateCodes.stcSuccess)
                throw new AlfaException(code, message);
        }
        /// <summary>
        /// Обертка для метода Терминала, для отподписки от таблицы. Внутри ставится блокировка.
        /// </summary>
        /// <param name="tableName">Имя таблицы</param>
        /// <param name="where">фильтр в виде sql строки</param>
        private void UnSubscribeTable(string tableName, string where)
        {
            string message;
            tagStateCodes code;

            // ставим глобальную блокировку для СОМ объекта.
            lock (_globalLock)
            {
                code = _ad.UnSubscribeTable(tableName, where, out message);
            }


            if (code != tagStateCodes.stcSuccess)
                throw new AlfaException(code, message);
        }

        #endregion Методы обертки для методов COM объекта, с блокировкой.

        #region Создание, отмена ордеров

        private class IdOrderPair
        {
            public long Id;
            public Order Order;
        }
        // ордера ожидающие подтверждения от брокера
        private readonly SynchronizedCollection<IdOrderPair> _confirmationWaitingOrders = new SynchronizedCollection<IdOrderPair>();

        /// <summary>
        /// Создать лимитированную/рыночную заявку.
        /// </summary>
        /// <param name="order">Ордер который нужно зарегистрировать на бирже.</param>
        /// <exception cref="ArgumentNullException">Если аргумент не задан.</exception>
        /// <exception cref="Exception">Если ордер неподдерживаемого типа.</exception>
        public void CreateLimitOrder(Order order)
        {
            #region Проверка входных аргументов
            if (order.IsNull())
            {
                throw new ArgumentNullException("order");
            }

            if (!order.IsLimit() && !order.IsMarket())
            {
                throw new Exception("CreateLimitOrder может содержать только Limit/Market заявку.");
            }
            #endregion

            #region Получение необходимых параметров для регистрации ордера
            var account = order.Portfolio.Name.Split("@")[0]; // Портфель
            var placeCode = order.Security.GetPlaceCode(); // Код торговой площадки (рынка).
            var pCode = order.Security.Code; // Код финансового инструмента.
            var endDate = AlfaUtils.CancelTimeToAlfa(order.ExpiryDate); // Срок действия поручения.
            var comments = order.Comment; // Комментарий.
            var currency = order.Security.GetCurrency(); // Валюта цены.
            var buySell = AlfaUtils.OrderDirectionsToAlfa(order.Direction); // Покупка/Продажа
            var quantity = (int)order.Volume; // Количество.
            var price = order.Price; // Цена.

            // расчитаем цену ордера для рыночной заявки.
            // Проблема расчета в том, что если хватануть слишком много, можем попасть вне лимитов цены того же фьючерса.
            // Отсюда пробуем взять макс цену инструмента за сессию, или 100 пунктов сверху на покупку, и обратное на продажу.
            if (order.IsMarket())
            {
                var sec = order.Security;
                if (order.Direction == OrderDirections.Buy)
                {
                    // best ask + 100 min price steps или sec.MaxPrice
                    var punktPrice = sec.BestAsk.Price + 100 * sec.MinStepSize;
                    price = sec.HighPrice > punktPrice ? sec.HighPrice : punktPrice;
                }
                else
                {
                    // best bid - 100 min price steps
                    var punktPrice = sec.BestBid.Price - 100 * sec.MinStepSize;
                    price = sec.LowPrice < punktPrice ? sec.LowPrice : punktPrice;
                }

            }
            #endregion

            #region производим непосредственную регистрацию ордера.
            int ordId;
            StateCodes result;
            string resultMessage;
            
            //Debug.WriteLine("{2}: AlfaWrapper - Начинаю регистрацию завки {0} на {1}.".Put(comments, buySell, MarketTime.ToString()));
            lock (_globalLock)
            {
                ordId = _ad.CreateLimitOrder(account, placeCode, pCode, endDate, comments, currency, buySell, quantity, (double)price,
                                             null, null, null, null, null, "Y", null, null, null, null, null, null, null, null,
                                             null, null, -1);
                result = _ad.LastResult;
                resultMessage = _ad.LastResultMsg;
            }

            // Заявку подаем без таймаута. Метод дожен обязательно вернуть внутренний номер ордера. Иначе точно глюк.
            if (ordId == 0)
            {
                //Debug.WriteLine("{4}: AlfaWrapper - Ошибка регистрации завки {0} на {1}. OrdNo: {2} Message: {3} Result: ".Put(comments, buySell, ordId, resultMessage, MarketTime.ToString()));

                var exception = new Exception(resultMessage);
                var orderFail = new OrderFail { Order = order, Error = exception };
                _registerOrderFailedQueue.TryAdd(orderFail);
            }
            else
            {
                //Debug.WriteLine("{4}: AlfaWrapper - Удачная регистрация завки {0} на {1}. OrdNo: {2} Message: {3} Result: ".Put(comments, buySell, ordId, resultMessage, MarketTime.ToString()));
                // помещаем заявку в очередь подтверждения
                _confirmationWaitingOrders.Add(new IdOrderPair() {Id = ordId, Order = order});
            }
            
            #endregion
        }
        /// <summary>
        /// Перерегистрировать лимитированную заявку. То есть изменить параметры заявки.
        /// </summary>
        /// <param name="oldOrder">Ордер который нужно изменить.</param>
        /// <param name="newOrder">Новые параметры ордера.</param>
        /// <exception cref="ArgumentNullException">Если аргумент не задан.</exception>
        /// <exception cref="Exception">Если ордер неподдерживаемого типа.</exception>
        public void UpdateLimitOrder(Order oldOrder, Order newOrder)
        {
            #region Проверка входных аргументов
            // проверим заданы ли входные аргументы.
            if (oldOrder.IsNull())
            {
                throw new ArgumentNullException("oldOrder");
            }

            if (newOrder.IsNull())
            {
                throw new ArgumentNullException("newOrder");
            }

            // проверим соответствие типов ордеров
            if (!oldOrder.IsLimit() && !oldOrder.IsMarket())
            {
                throw new Exception("UpdateLimitOrder может обновить только Limit/Market заявку.");
            }
            if (!newOrder.IsLimit() && !newOrder.IsMarket())
            {
                throw new Exception("UpdateLimitOrder может обновить только Limit/Market заявку.");
            }
            #endregion

            var endDate = AlfaUtils.CancelTimeToAlfa(newOrder.ExpiryDate);

            #region Непосредственная регистрация ордера
            int ordNo;
            StateCodes result;
            string resultMessage;

            lock (_globalLock)
            {
                // ждем ответа до посинения. Если по таймауту отваливаться, может быть что ордер принят а мы думаем что нет.
                ordNo = _ad.UpdateLimitOrder((int)oldOrder.Id, newOrder.Price, newOrder.Volume, endDate, 0);
                result = _ad.LastResult;
                resultMessage = _ad.LastResultMsg;
            }

            // если заявку подаем с таймаутом, и номер ордера 0, значит была ошибка регистрации.
            if (ordNo == 0)
            {
                var exception = new Exception(resultMessage);
                var newFail = new OrderFail { Order = newOrder, Error = exception };
                _registerOrderFailedQueue.TryAdd(newFail);
            }
            else
            {
                // присвоим номер заявки сразу, чтобы можно было отменить заявку сразу же после выставления.
                newOrder.Id = ordNo;
            }

            #endregion
        }

        public void AsyncCreateLimitOrder(Order order)
        {
            var command = new CreateLimitOrderCommand(this, order);
            
            // пробуем поставить заявку в очередь. Если за отведенное время не удалось вызываем событие ошибки регистрации.
            // тоже самое делаем если при добавлении в очередь произошла ошибка.
            try
            {
                var result = _orderQueue.TryAdd(command, 1000);
                if (!result)
                {
                    var exception = new TimeoutException("Заявка не была поставлена в очередь за отведенное время.");
                    var fail = new OrderFail { Order = order, Error = exception };
                    _registerOrderFailedQueue.TryAdd(fail);
                }
            }
            catch (Exception exception)
            {
                var fail = new OrderFail { Order = order, Error = exception };
                _registerOrderFailedQueue.TryAdd(fail);
            }
        }

        public void AsyncUpdateLimitOrder(Order oldOrder, Order newOrder)
        {
            var command = new UpdateLimitOrderCommand(this, oldOrder, newOrder);

            // пробуем поставить заявку в очередь. Если за отведенное время не удалось вызываем событие ошибки регистрации.
            // тоже самое делаем если при добавлении в очередь произошла ошибка.
            try
            {
                var result = _orderQueue.TryAdd(command, 1000);
                if (!result)
                {
                    var exception = new TimeoutException("Заявка не была поставлена в очередь за отведенное время.");
                    var newFail = new OrderFail { Order = newOrder, Error = exception };
                    _registerOrderFailedQueue.TryAdd(newFail);
                }
            }
            catch (Exception exception)
            {
                var newFail = new OrderFail { Order = newOrder, Error = exception };
                var list = new List<OrderFail>(1) { newFail };
                _registerOrderFailedQueue.TryAdd(newFail);
            }
        }


        /// <summary>
        /// Создать стоп заявку.
        /// </summary>
        /// <param name="order">Ордер который нужно зарегистрировать на бирже.</param>
        /// <exception cref="ArgumentNullException">Если аргумент не задан.</exception>
        /// <exception cref="Exception">Если ордер неподдерживаемого типа.</exception>
        public void CreateStopOrder(Order order)
        {
            #region Проверка входных аргументов
            if (order.IsNull())
            {
                throw new ArgumentNullException("order");
            }

            if (!order.IsStop())
            {
                throw new Exception("CreateStopOrder может создавать только стоп заявки.");
            }
            #endregion

            #region Получение необходимых величин для регистрации ордера
            var account = order.Portfolio.Name.Split("@")[0]; // Портфель
            var placeCode = order.Security.GetPlaceCode(); // Код торговой площадки (рынка).
            var pCode = order.Security.Code; // Код финансового инструмента.
            var endDate = AlfaUtils.CancelTimeToAlfa(order.ExpiryDate); // Срок действия поручения.
            var comments = order.Comment; // Комментарий.
            var currency = order.Security.GetCurrency(); // Валюта цены.
            var buySell = AlfaUtils.OrderDirectionsToAlfa(order.Direction); // Покупка/Продажа
            var quantity = (int)order.Volume; // Количество.
            //var price = order.Price; // Цена.

            //Logger.Debug(account + ", " + placeCode + ", " + pCode + ", " + endDate + ", " + comments + ", " + currency + ", " +
            //				buySell + ", " + quantity + ", " + price);

            var condition = (AlfaStopCondition)order.StopCondition;
            var slippage = condition.Slippage; // Проскальзывание
            var takeProfit = condition.TakeProfit; // Цена фиксирования прибыли
            var stopPrice = condition.StopPrice;
            #endregion

            //Logger.Debug(slippage + ", " + takeProfit, _logTag);

            #region производим непосредственную регистрацию ордера.
            int ordId;
            StateCodes result;
            string resultMessage;

            // Debug.WriteLine("{2}: AlfaWrapper - Начинаю регистрацию стоп завки {0} на {1}.".Put(comments, buySell, MarketTime.ToString()));
            lock (_globalLock)
            {
                ordId = _ad.CreateStopOrder(account, placeCode, pCode, endDate, comments,
                                currency, buySell, quantity, stopPrice, slippage, takeProfit, -1);
                result = _ad.LastResult;
                resultMessage = _ad.LastResultMsg;
            }

            // Заявку подаем без таймаута. Метод дожен обязательно вернуть внутренний номер ордера. Иначе точно глюк.
            if (ordId == 0)
            {
                // Debug.WriteLine("{4}: AlfaWrapper - Ошибка регистрации стоп завки {0} на {1}. OrdNo: {2} Message: {3} Result: ".Put(comments, buySell, ordId, resultMessage, MarketTime.ToString()));

                var exception = new Exception(resultMessage);
                var orderFail = new OrderFail { Order = order, Error = exception };
                _registerOrderFailedQueue.TryAdd(orderFail);
            }
            else
            {
                // Debug.WriteLine("{4}: AlfaWrapper - Удачная регистрация стоп завки {0} на {1}. OrdNo: {2} Message: {3} Result: ".Put(comments, buySell, ordId, resultMessage, MarketTime.ToString()));

                // помещаем заявку в очередь подтверждения
                _confirmationWaitingOrders.Add(new IdOrderPair() { Id = ordId, Order = order });
            }

            #endregion
        }
        /// <summary>
        /// Перерегистрировать стоп заявку. То есть обновить параметры заявки.
        /// </summary>
        /// <param name="oldOrder">Ордер который нужно изменить.</param>
        /// <param name="newOrder">Новые параметры ордера.</param>
        /// <exception cref="ArgumentNullException">Если аргумент не задан.</exception>
        /// <exception cref="Exception">Если ордер неподдерживаемого типа.</exception>
        public void UpdateStopOrder(Order oldOrder, Order newOrder)
        {
            #region Проверка входных аргументов
            // проверим заданы ли входные аргументы.
            if (oldOrder.IsNull())
            {
                throw new ArgumentNullException("oldOrder");
            }

            if (newOrder.IsNull())
            {
                throw new ArgumentNullException("newOrder");
            }

            // проверим соответствие типов ордеров
            if (!oldOrder.IsStop())
            {
                throw new Exception("UpdateStopOrder может обновить только Stop заявку.");
            }
            if (!newOrder.IsStop())
            {
                throw new Exception("UpdateStopOrder может обновить только Stop заявку.");
            }
            #endregion

            #region Получение необходимых параметров для регистрации ордера
            var newEndDate = AlfaUtils.CancelTimeToAlfa(newOrder.ExpiryDate);
            var newStopPrice = ((AlfaStopCondition)newOrder.StopCondition).StopPrice;
            var newSlippage = ((AlfaStopCondition)newOrder.StopCondition).Slippage;
            var newTakeProfit = ((AlfaStopCondition)newOrder.StopCondition).TakeProfit;
            #endregion

            #region Производим непосредственную регистрацию ордера.
            int ordNo;
            StateCodes result;
            string resultMessage;

            lock (_globalLock)
            {
                ordNo = _ad.UpdateStopOrder((int)oldOrder.Id, newStopPrice, newSlippage, newTakeProfit, 
                                            newOrder.Volume, newEndDate, 30);
                result = _ad.LastResult;
                resultMessage = _ad.LastResultMsg;
            }
            // если заявку подаем с таймаутом, и номер ордера 0, значит была ошибка регистрации.
            if (ordNo == 0)
            {
                var exception = new Exception(resultMessage);
                var fail = new OrderFail { Order = newOrder, Error = exception };
                _registerOrderFailedQueue.TryAdd(fail);
            }
            else
            {
                // присвоим номер заявки сразу, чтобы можно было отменить заявку сразу же после выставления.
                newOrder.Id = ordNo;
            }

            #endregion
        }

        public void AsyncCreateStopOrder(Order order)
        {
            var command = new CreateStopOrderCommand(this, order);

            // пробуем поставить заявку в очередь. Если за отведенное время не удалось вызываем событие ошибки регистрации.
            // тоже самое делаем если при добавлении в очередь произошла ошибка.
            try
            {
                var result = _orderQueue.TryAdd(command, 1000);
                if (!result)
                {
                    var exception = new TimeoutException("Заявка не была поставлена в очередь за отведенное время.");
                    var fail = new OrderFail { Order = order, Error = exception };
                    _registerOrderFailedQueue.TryAdd(fail);
                }
            }
            catch (Exception exception)
            {
                var fail = new OrderFail { Order = order, Error = exception };
                _registerOrderFailedQueue.TryAdd(fail);
            }

        }

        public void AsyncUpdateStopOrder(Order oldOrder, Order newOrder)
        {
            var command = new UpdateStopOrderCommand(this, oldOrder, newOrder);

            // пробуем поставить заявку в очередь. Если за отведенное время не удалось вызываем событие ошибки регистрации.
            // тоже самое делаем если при добавлении в очередь произошла ошибка.
            try
            {
                var result = _orderQueue.TryAdd(command, 1000);
                if (!result)
                {
                    var exception = new TimeoutException("Заявка не была поставлена в очередь за отведенное время.");
                    var newFail = new OrderFail { Order = newOrder, Error = exception };
                    _registerOrderFailedQueue.TryAdd(newFail);
                }
            }
            catch (Exception exception)
            {
                var newFail = new OrderFail { Order = newOrder, Error = exception };
                _registerOrderFailedQueue.TryAdd(newFail);
            }
        }


        /// <summary>
        /// Создать следящую заявку.
        /// </summary>
        /// <param name="order"></param>
        public void CreateTrailingOrder(Order order)
        {
            #region Проверка входных аргументов
            if (order.IsNull())
            {
                throw new ArgumentNullException("order");
            }

            if (!order.IsTrailingStop())
            {
                throw new Exception("CreateStopOrder может создавать только трейлинг стоп заявки.");
            }
            #endregion

            #region Получение необходимых величин для регистрации ордера
            var account = order.Portfolio.Name.Split("@")[0]; // Портфель
            var placeCode = order.Security.GetPlaceCode(); // Код торговой площадки (рынка).
            var pCode = order.Security.Code; // Код финансового инструмента.
            var endDate = AlfaUtils.CancelTimeToAlfa(order.ExpiryDate); // Срок действия поручения.
            var comments = order.Comment; // Комментарий.
            var currency = order.Security.GetCurrency(); // Валюта цены.
            var buySell = AlfaUtils.OrderDirectionsToAlfa(order.Direction); // Покупка/Продажа
            var quantity = (int)order.Volume; // Количество.

            //Logger.Debug(account + ", " + placeCode + ", " + pCode + ", " + endDate + ", " + comments + ", " + currency + ", " +
            //				buySell + ", " + quantity + ", " + price);

            var condition = (AlfaStopCondition)order.StopCondition;
            var slippage = condition.Slippage; // Проскальзывание
            var trailingLevel = condition.TrailingLevel; // Величина трейлинг уровня.
            var stopPrice = condition.StopPrice;
            // стоп цена заявки отличается от цены на уровень трейлинглевел.
            var startPrice = (order.Direction == OrderDirections.Buy)
                                 ? stopPrice - trailingLevel
                                 : stopPrice + trailingLevel;
            #endregion

            //Logger.Debug(slippage + ", " + takeProfit, _logTag);

            #region производим непосредственную регистрацию ордера.
            int ordId;
            StateCodes result;
            string resultMessage;

            lock (_globalLock)
            {
                ordId = _ad.CreateTrailingOrder(account, placeCode, pCode, endDate, comments,
                                currency, buySell, quantity, (double)startPrice, (double)trailingLevel, slippage, -1);
                result = _ad.LastResult;
                resultMessage = _ad.LastResultMsg;
            }

            // Заявку подаем без таймаута. Метод дожен обязательно вернуть внутренний номер ордера. Иначе точно глюк.
            if (ordId == 0)
            {
                // Debug.WriteLine("{4}: AlfaWrapper - Ошибка регистрации стоп завки {0} на {1}. OrdNo: {2} Message: {3} Result: ".Put(comments, buySell, ordId, resultMessage, MarketTime.ToString()));

                var exception = new Exception(resultMessage);
                var orderFail = new OrderFail { Order = order, Error = exception };
                _registerOrderFailedQueue.TryAdd(orderFail);
            }
            else
            {
                // Debug.WriteLine("{4}: AlfaWrapper - Удачная регистрация стоп завки {0} на {1}. OrdNo: {2} Message: {3} Result: ".Put(comments, buySell, ordId, resultMessage, MarketTime.ToString()));

                // помещаем заявку в очередь подтверждения
                _confirmationWaitingOrders.Add(new IdOrderPair() { Id = ordId, Order = order });
            }

            #endregion
        }
        /// <summary>
        /// Перерегистрировать следящую заявку. То есть изменить параметры заявки.
        /// Реально изменить только TrailingLevel, Slippage, Volume, EndDate параметры.
        /// На ММВБ вообще нельзя модифицировать следящие заявки.
        /// </summary>
        /// <param name="oldOrder"></param>
        /// <param name="newOrder"></param>
        public void UpdateTrailingOrder(Order oldOrder, Order newOrder)
        {
            #region Проверка входных аргументов
            // проверим заданы ли входные аргументы.
            if (oldOrder.IsNull())
            {
                throw new ArgumentNullException("oldOrder");
            }

            if (newOrder.IsNull())
            {
                throw new ArgumentNullException("newOrder");
            }

            // проверим соответствие типов ордеров
            if (!oldOrder.IsTrailingStop())
            {
                throw new Exception("UpdateStopOrder может обновить только Stop заявку.");
            }
            if (!newOrder.IsTrailingStop())
            {
                throw new Exception("UpdateStopOrder может обновить только Stop заявку.");
            }
            #endregion

            #region Получение необходимых величин для регистрации ордера
            var newEndDate = AlfaUtils.CancelTimeToAlfa(newOrder.ExpiryDate);
            var newSlippage = ((AlfaStopCondition)newOrder.StopCondition).Slippage;
            var newTrailingLevel = ((AlfaStopCondition)newOrder.StopCondition).TrailingLevel;
            #endregion

            #region Производим непосредственную регистрацию ордера.
            int ordNo;
            StateCodes result;
            string resultMessage;

            lock (_globalLock)
            {
                ordNo = _ad.UpdateTrailingOrder((int)oldOrder.Id, newTrailingLevel, newSlippage, newOrder.Volume, newEndDate, 30);
                result = _ad.LastResult;
                resultMessage = _ad.LastResultMsg;
            }
            // если заявку подаем с таймаутом, и номер ордера 0, значит была ошибка регистрации.
            if (ordNo == 0)
            {
                var exception = new Exception(resultMessage);
                var fail = new OrderFail { Order = newOrder, Error = exception };
                _registerOrderFailedQueue.TryAdd(fail);
            }
            else
            {
                // присвоим номер заявки сразу, чтобы можно было отменить заявку сразу же после выставления.
                newOrder.Id = ordNo;
            }

            #endregion
        }

        public void AsyncCreateTrailingOrder(Order order)
        {
            var command = new CreateTrailingOrderCommand(this, order);

            // пробуем поставить заявку в очередь. Если за отведенное время не удалось вызываем событие ошибки регистрации.
            // тоже самое делаем если при добавлении в очередь произошла ошибка.
            try
            {
                var result = _orderQueue.TryAdd(command, 1000);
                if (!result)
                {
                    var exception = new TimeoutException("Заявка не была поставлена в очередь за отведенное время.");
                    var fail = new OrderFail { Order = order, Error = exception };
                    _registerOrderFailedQueue.TryAdd(fail);
                }
            }
            catch (Exception exception)
            {
                var fail = new OrderFail { Order = order, Error = exception };
                _registerOrderFailedQueue.TryAdd(fail);
            }

        }

        public void AsyncUpdateTrailingOrder(Order oldOrder, Order newOrder)
        {
            var command = new UpdateTrailingOrderCommand(this, oldOrder, newOrder);

            // пробуем поставить заявку в очередь. Если за отведенное время не удалось вызываем событие ошибки регистрации.
            // тоже самое делаем если при добавлении в очередь произошла ошибка.
            try
            {
                var result = _orderQueue.TryAdd(command, 1000);
                if (!result)
                {
                    var exception = new TimeoutException("Заявка не была поставлена в очередь за отведенное время.");
                    var newFail = new OrderFail { Order = newOrder, Error = exception };
                    _registerOrderFailedQueue.TryAdd(newFail);
                }
            }
            catch (Exception exception)
            {
                var newFail = new OrderFail { Order = newOrder, Error = exception };
                _registerOrderFailedQueue.TryAdd(newFail);
            }
        }


        public void CancelOrder(Order order)
        {
            if (order == null)
                throw new ArgumentNullException("order");

            #region Непосредственная отмена ордера
            StateCodes result;
            string resultMessage;

            // Debug.WriteLine("{0}: AlfaWrapper - Начинаю отмену заявки OrdNo: {1} TransId: {2}.".Put(MarketTime.ToString(), order.Id, order.TransactionId));
            lock (_globalLock)
            {
                _ad.DropOrder(order.Id, null, null, null, null, null, -1);
                result = _ad.LastResult;
                resultMessage = _ad.LastResultMsg;
            }

            // отмену делаем без ожидания. Значит если статус не Успешен, точно глюк.
            if (result == StateCodes.stcSuccess)
            {
                // Debug.WriteLine("{0}: AlfaWrapper - Удачная отмена заявки OrdNo: {1} TransId: {2} Message: {3}.".Put(MarketTime.ToString(), order.Id, order.TransactionId, resultMessage));

                // помещаем заявку в очередь подтверждения
                _confirmationWaitingOrders.Add(new IdOrderPair() { Id = -1, Order = order });
            }
            else
            {
                // Debug.WriteLine( "{0}: AlfaWrapper - Ошибка отмены заявки OrdNo: {1} TransId: {2} Message: {3}.".Put(MarketTime.ToString(), order.Id, order.TransactionId, resultMessage));

                var exception = new Exception(resultMessage);
                var orderFail = new OrderFail {Order = order, Error = exception};
                _cancelOrderFailedQueue.TryAdd(orderFail);
            }

            #endregion
        }

        public void CancelOrders(bool? isStopOrder, Portfolio portfolio, OrderDirections? direction, string classCode, Security security)
        {
            var isBuySell = direction == null ? null : (direction == OrderDirections.Buy ? "B" : "S");
            _ad.DropOrder(null, isBuySell, portfolio == null ? null : portfolio.Name.Split("-")[0], null, classCode, security == null ? null : security.GetPaperNo(), -1);
        }

        public void AsyncCancelOrder(Order order)
        {
            var command = new CancelOrderCommand(this, order);

            // пробуем поставить заявку в очередь. Если за отведенное время не удалось вызываем событие ошибки регистрации.
            // тоже самое делаем если при добавлении в очередь произошла ошибка.
            try
            {
                var result = _orderQueue.TryAdd(command, 1000);
                if (!result)
                {
                    var exception = new TimeoutException("Заявка не была поставлена в очередь за отведенное время.");
                    var fail = new OrderFail { Order = order, Error = exception };
                    _cancelOrderFailedQueue.TryAdd(fail);
                }
            }
            catch (Exception exception)
            {
                var fail = new OrderFail { Order = order, Error = exception };
                _cancelOrderFailedQueue.TryAdd(fail);
            }
        }

        #endregion Создание, отмена ордеров

        #region Получение исторических данных

        public string GetHistoryData(Security security, AlfaTimeFrames timeFrame, Range<DateTime> range)
        {
            const int modeStoreLocal = 2;
            const int maxWaitTimeSec = 10 * 60;

            //Trace.WriteLine("Start GetHistoryData: {0} - {1}".Put(range.Min, range.Max));
            string data;
            lock (_globalLock)
            {
                data = (string)_ad.GetArchiveFinInfo(security.GetPlaceCode(), security.Code,
                timeFrame.Interval, range.Min, range.Max, modeStoreLocal, maxWaitTimeSec);
            }

            //Trace.WriteLine("End GetHistoryData: {0} - {1}".Put(range.Min, range.Max));

            return data;
        }

        public string GetLocalHistoryData(Security security, AlfaTimeFrames timeFrame, Range<DateTime> range)
        {
            //Trace.WriteLine("Start GetLocalHistoryData: {0} - {1}".Put(range.Min, range.Max));

            var data = _ad.GetArchiveFinInfoFromDB(security.GetPlaceCode(), security.Code,
                                                   timeFrame.Interval, range.Min, range.Max);

            //Trace.WriteLine("End GetLocalHistoryData: {0} - {1}".Put(range.Min, range.Max));

            if (_ad.LastResult != StateCodes.stcSuccess)
            {
                Trace.WriteLine("ERROR fetching local data: {0}".Put(_ad.LastResultMsg));
            }

            return data;
        }

        #endregion Получение исторических данных

        #region Обработка очередей

        public void StartQueues()
        {
            _registerOrderFailedQueue = new BlockingCollection<OrderFail>();
            _cancelOrderFailedQueue = new BlockingCollection<OrderFail>();
            _orderQueue = new BlockingCollection<BaseCommand>();
            _tablesQueue = new BlockingCollection<TableDataPortion>();

            #region Start Tasks

            Task.Factory.StartNew(() =>
            {
                while (!_registerOrderFailedQueue.IsCompleted)
                {
                    // пробуем взять следующую порцию данных. Это может вызвать исключение.
                    // Исключения в процессе обработки порции данных должны обрабатываться внутри метода.
                    // Необработанные исключения приведут к завершению потока.
                    try
                    {
                        var orderFail = _registerOrderFailedQueue.Take();
                        ProcessRegisterFail(orderFail);
                    }
                    catch (InvalidOperationException ex)
                    {
                        Trace.WriteLine("ROFQ: " + ex.Message);
                        break;
                    }
                }
            });

            Task.Factory.StartNew(() =>
            {
                while (!_cancelOrderFailedQueue.IsCompleted)
                {
                    // пробуем взять следующую порцию данных. Это может вызвать исключение.
                    // Исключения в процессе обработки порции данных должны обрабатываться внутри метода.
                    // Необработанные исключения приведут к завершению потока.
                    try
                    {
                        var orderFail = _cancelOrderFailedQueue.Take();
                        ProcessCancelFail(orderFail);
                    }
                    catch (InvalidOperationException ex)
                    {
                        Trace.WriteLine("OCFQ: " + ex.Message);
                        break;
                    }
                }
            });

            Task.Factory.StartNew(() =>
            {
                while (!_orderQueue.IsCompleted)
                {
                    // пробуем взять следующую порцию данных. Это может вызвать исключение.
                    // Исключения в процессе обработки порции данных должны обрабатываться внутри метода.
                    // Необработанные исключения приведут к завершению потока.
                    try
                    {
                        var info = _orderQueue.Take();
                        ProcessCommand(info);
                    }
                    catch (InvalidOperationException ex)
                    {
                        Trace.WriteLine("CMDQ: " + ex.Message);
                        break;
                    }
                }
            });

            Task.Factory.StartNew(() =>
            {
                while (!_tablesQueue.IsCompleted)
                {
                    // пробуем взять следующую порцию данных. Это может вызвать исключение.
                    // Исключения в процессе обработки порции данных должны обрабатываться внутри метода.
                    // Необработанные исключения приведут к завершению потока.
                    try
                    {
                        var info = _tablesQueue.Take();
                        ProcessTableDataPortion(info);
                    }
                    catch (InvalidOperationException ex)
                    {
                        Trace.WriteLine("TBLQ: " + ex.Message);
                        break;
                    }
                }
            });
            #endregion
        }

	    public void StopQueues()
        {
            // TODO: завести пул тасков и отключать перебором пула.
            _registerOrderFailedQueue.CompleteAdding();
            _cancelOrderFailedQueue.CompleteAdding();
            _orderQueue.CompleteAdding();
            _tablesQueue.CompleteAdding();
        }

        private void ProcessCancelFail(OrderFail orderFail)
        {
            // Метод выполняется в блоке трай кэтч, потом что исключение будет ложить таск, если его не обработать
            // Метод вызывается из фонового таска.       
            try
            {
                if (orderFail.IsNull())
                    throw new ArgumentNullException("orderFail");

                var listFailed = new List<OrderFail>(1) { orderFail };

                if (orderFail.Order.Type == OrderTypes.Conditional)
                    StopOrdersCancelFailedEvent.SafeInvoke(listFailed);
                else
                    OrdersCancelFailedEvent.SafeInvoke(listFailed);
            }
            catch (Exception exception)
            {
                Trace.WriteLine("Исключение в очереди ошибок отмены ордеров {0}".Put(exception.Message));
            }
        }

        private void ProcessRegisterFail(OrderFail orderFail)
        {
            // Метод выполняется в блоке трай кэтч, потом что исключение будет ложить таск, если его не обработать
            // Метод вызывается из фонового таска.       
            try
            {
                if (orderFail.IsNull())
                    throw new ArgumentNullException("orderFail");


                var listFailed = new List<OrderFail>(1) { orderFail };

                if (orderFail.Order.Type == OrderTypes.Conditional)
                    StopOrdersRegisterFailedEvent.SafeInvoke(listFailed);
                else
                    OrdersRegisterFailedEvent.SafeInvoke(listFailed);
            }
            catch (Exception exception)
            {
                Trace.WriteLine("Исключение в очереди ошибок регистрации ордеров {0}".Put(exception.Message));
            }

        }

	    private void ProcessTableDataPortion(TableDataPortion dataPortion)
	    {
                // Метод выполняется в блоке трай кэтч, потом что исключение будет ложить таск, если его не обработать
            // Метод вызывается из фонового таска.
            try
            {
                if (dataPortion.IsNull())
                    throw new ArgumentNullException("dataPortion");

                
                var tableName = dataPortion.TableName;
                
                if (string.IsNullOrWhiteSpace(tableName))
                    return;

                // генерим событие о получении новых некривых данных.
                NewDataExportedEvent.SafeInvoke();


                // TODO: перебор таблиц сделать НЕ хардкодед. Завести пул таблиц.

                // таблицы размещены в порядке важности. Наиболее скоростные таблицы идут первыми.

                // queue
                // При выборке стакана возвращается в paper_no=0, поэтому данные в подписчик не попадают, 
                // так как он фильтрует их по paper_no. Используем специальную версию парсинга.
                // paper_no обязательно должен присутствовать при подписке на queue иначе данных не будет приходить.
                if (tableName.ToLower() == _tblQueue.Name.ToLower())
                {
                    _tblQueue.PushNewData(dataPortion);
                    return;
                }

                // all_trades
                if (tableName.ToLower() == _tblAllTrades.Name.ToLower())
                {
                    _tblAllTrades.PushNewData(dataPortion);
                    return;
                }

                // fin_info
                if (tableName.ToLower() == _tblFinInfo.Name.ToLower())
                {
                    _tblFinInfo.PushNewData(dataPortion);
                    return;
                }

                // balance
                if (tableName.ToLower() == _tblBalance.Name.ToLower())
                {
                    _tblBalance.PushNewData(dataPortion);
                    return;
                }

                // trades
                if (tableName.ToLower() == _tblMyTrades.Name.ToLower())
                {
                    _tblMyTrades.PushNewData(dataPortion);
                    return;
                }

                // orders
                if (tableName.ToLower() == _tblOrders.Name.ToLower())
                {
                    // событие изенения таблицы в котором будет ID ордера приходит чуть позже чем событие OnOrderConfirmed. 
                    // А уж подтверждение ордера приходит аж через 3 секунды со статусом итд.
                    // имеет смысл ориентир держать на событие OnOrderConfirmed для проверки принят ордер или нет.
                    //Debug.WriteLine("Таблица изменилась: {0}:{1}:{2}".Put(DateTime.Now.Minute, DateTime.Now.Second, DateTime.Now.Millisecond));
                    _tblOrders.PushNewData(dataPortion);
                    return;
                }
            }
            catch (Exception exception)
            {
                // в случае необработанного исключения, генерим событие об ошибке обработки данных.
                ProcessDataErrorEvent.SafeInvoke(exception);
            }
	    }

	    private void ProcessCommand(BaseCommand command)
	    {
            var cmd = command;

            // Метод выполняется в блоке трай кэтч, потом что исключение будет ложить таск, если его не обработать
            // Метод вызывается из фонового таска.       
	        try
	        {
	            if (cmd.IsNull())
	                throw new ArgumentNullException("command");

                cmd.Execute();
	        }
	        catch (Exception exception)
	        {
	            Trace.WriteLine("Исключение в очереди команд {0}".Put(exception.Message));
	        }
	    }

	    #endregion Обработка очередей

	}
}
